using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using GameFrame.Deterministic;
using GameFrame.Core;
using FP = GameFrame.Deterministic.FP;
using FPMath = GameFrame.Deterministic.FPMath;
using FPVector2 = GameFrame.Deterministic.FPVector2;

namespace GameFrame
{
    /// <summary>
    /// Defines a 2D shape with <see cref="P:Quantum.Shape2D.Type" /> and data disposed in a union-like structure.
    /// All shapes have a <see cref="F:Quantum.Shape2D.UserTag" />, <see cref="F:Quantum.Shape2D.BroadRadius" /> and <see cref="F:Quantum.Shape2D.Centroid" />.
    /// All non-compound shapes have a <see cref="F:Quantum.Shape2D.LocalTransform" /> and their <see cref="F:Quantum.Shape2D.Centroid" /> always match their local transform position.
    /// </summary>
    /// 
    ///             \ingroup Physics2dApi
    [StructLayout(LayoutKind.Explicit)]
    public struct Shape2D
    {
        public const int SIZE = 64;
        [FieldOffset(0)]
        internal Shape2DType _type;
        [FieldOffset(1)]
        private byte _padding1;
        /// <summary>Set a number tag to identify a shape.</summary>
        [FieldOffset(2)]
        public ushort UserTag;
        [FieldOffset(4)]
        private int _padding2;
        [FieldOverlap(8)]
        [FieldOffset(8)]
        public Transform2D LocalTransform;
        [FieldOverlap(8)]
        [FieldOffset(8)]
        public FPVector2 Centroid;
        [FieldOverlap(8)]
        [FramePrinter.PrintIf("_type", 16, FramePrinter.PrintIfAttribute.CompareOperator.Equal)]
        [FieldOffset(8)]
        public Shape2D.CompoundShape2D Compound;
        private const int SHAPE_DATA = 32;
        [FieldOverlap(8)]
        [FieldOffset(32)]
        public FP BroadRadius;
        [FieldOverlap(8)]
        [FramePrinter.PrintIf("_type", 1, FramePrinter.PrintIfAttribute.CompareOperator.Equal)]
        [FieldOffset(32)]
        public Shape2D.CircleShape Circle;
        [FieldOverlap(8)]
        [FramePrinter.PrintIf("_type", 4, FramePrinter.PrintIfAttribute.CompareOperator.Equal)]
        [FieldOffset(32)]
        public Shape2D.BoxShape Box;
        [FieldOverlap(8)]
        [FramePrinter.PrintIf("_type", 2, FramePrinter.PrintIfAttribute.CompareOperator.Equal)]
        [FieldOffset(32)]
        public Shape2D.PolygonShape Polygon;
        [FieldOverlap(8)]
        [FramePrinter.PrintIf("_type", 8, FramePrinter.PrintIfAttribute.CompareOperator.Equal)]
        [FieldOffset(32)]
        public Shape2D.EdgeShape Edge;

        public Shape2DType Type
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._type;
        }

        public static Shape2D CreateCircle(FP radius, FPVector2 posOffset = default (FPVector2)) => new Shape2D()
        {
            _type = Shape2DType.Circle,
            Circle = new Shape2D.CircleShape() { Radius = radius },
            LocalTransform = new Transform2D()
            {
                Position = posOffset,
                Rotation = (FP) 0
            }
        };

        public static Shape2D CreatePolygon(PolygonCollider asset, FPVector2 posOffset = default (FPVector2), FP rotOffset = default (FP)) => new Shape2D()
        {
            _type = Shape2DType.Polygon,
            Polygon = new Shape2D.PolygonShape()
            {
                _assetRef = (AssetRefPolygonCollider) asset,
                _broadRadius = asset.Radius,
                _centroid = asset.Centroid
            },
            LocalTransform = new Transform2D()
            {
                Position = posOffset,
                Rotation = rotOffset
            }
        };

        public static Shape2D CreateBox(FPVector2 extents, FPVector2 posOffset = default (FPVector2), FP rotOffset = default (FP)) => new Shape2D()
        {
            _type = Shape2DType.Box,
            Box = new Shape2D.BoxShape()
            {
                Extents = extents,
                _broadRadius = extents.Magnitude
            },
            LocalTransform = new Transform2D()
            {
                Position = posOffset,
                Rotation = rotOffset
            }
        };

        public static Shape2D CreateEdge(FP extent, FPVector2 posOffset = default (FPVector2), FP rotOffset = default (FP)) => new Shape2D()
        {
            _type = Shape2DType.Edge,
            Edge = new Shape2D.EdgeShape()
            {
                Extent = FPMath.Max(extent, FP.EN3)
            },
            LocalTransform = new Transform2D()
            {
                Position = posOffset,
                Rotation = rotOffset
            }
        };

        public static Shape2D CreatePersistentCompound() => new Shape2D()
        {
            _type = Shape2DType.Compound,
            Compound = new Shape2D.CompoundShape2D()
            {
                _isPersistent = 1
            }
        };

        /// <summary>
        /// Computes a point on the shape which has the highest dot product in a given direction (<paramref name="localDir" />),
        /// both in the local space of the shape.
        /// </summary>
        /// <param name="f">Current frame.</param>
        /// <param name="localDir">The direction, in local space, in which to compute the support point.</param>
        /// <param name="skipNormalize">If the provided direction is already normalized.</param>
        /// <returns>A point, in the local space of the shape, which has the highest dot product in the given direction.</returns>
        public unsafe FPVector2 GetLocalSupport(FrameBase f, FPVector2 localDir, bool skipNormalize = false)
        {
            if (this._type == Shape2DType.Compound)
            {
                FPVector2 localSupport1 = new FPVector2();
                long num1 = long.MinValue;
                Shape2D* shapesBuffer;
                int count;
                if (this.Compound.GetShapes(f, out shapesBuffer, out count))
                {
                    int num2 = 0;
                    while (num2 < count)
                    {
                        FPVector2 localSupport2 = shapesBuffer->GetLocalSupport(f, localDir, skipNormalize);
                        long num3 = (localSupport2.X.RawValue * localDir.X.RawValue >> 16) + (localSupport2.Y.RawValue * localDir.Y.RawValue >> 16);
                        if (num3 > num1)
                        {
                            num1 = num3;
                            localSupport1 = localSupport2;
                        }
                        ++num2;
                        ++shapesBuffer;
                    }
                }
                return localSupport1;
            }
            long sinRaw = 0;
            long cosRaw = 0;
            if (this.LocalTransform.Rotation.RawValue != 0L)
            {
                FPMath.SinCosRaw(this.LocalTransform.Rotation, out sinRaw, out cosRaw);
                FPVector2 fpVector2 = localDir;
                localDir.X.RawValue = (fpVector2.X.RawValue * cosRaw >> 16) + (fpVector2.Y.RawValue * sinRaw >> 16);
                localDir.Y.RawValue = (fpVector2.Y.RawValue * cosRaw >> 16) - (fpVector2.X.RawValue * sinRaw >> 16);
            }
            FPVector2 polygonLocalSupport;
            switch (this._type)
            {
                case Shape2DType.Circle:
                    long rawValue = this.Circle.Radius.RawValue;
                    if (!skipNormalize)
                    {
                        long num = FPMath.SqrtRaw((localDir.X.RawValue * localDir.X.RawValue >> 16) + (localDir.Y.RawValue * localDir.Y.RawValue >> 16));
                        if (num > 0L)
                        {
                            localDir.X.RawValue = (localDir.X.RawValue << 16) / num;
                            localDir.Y.RawValue = (localDir.Y.RawValue << 16) / num;
                        }
                    }
                    polygonLocalSupport.X.RawValue = localDir.X.RawValue * rawValue >> 16;
                    polygonLocalSupport.Y.RawValue = localDir.Y.RawValue * rawValue >> 16;
                    break;
                case Shape2DType.Polygon:
                    polygonLocalSupport = FPVector2.CalculatePolygonLocalSupport(f.FindAsset<PolygonCollider>(this.Polygon._assetRef.Id).Vertices, ref localDir);
                    break;
                case Shape2DType.Box:
                    polygonLocalSupport.X.RawValue = localDir.X.RawValue < 0L ? -this.Box._extents.X.RawValue : this.Box._extents.X.RawValue;
                    polygonLocalSupport.Y.RawValue = localDir.Y.RawValue < 0L ? -this.Box._extents.Y.RawValue : this.Box._extents.Y.RawValue;
                    break;
                case Shape2DType.Edge:
                    polygonLocalSupport.X.RawValue = localDir.X.RawValue < 0L ? -this.Edge.Extent.RawValue : this.Edge.Extent.RawValue;
                    polygonLocalSupport.Y.RawValue = 0L;
                    break;
                default:
                    return new FPVector2();
            }
            if (this.LocalTransform.Rotation.RawValue != 0L)
            {
                FPVector2 fpVector2 = polygonLocalSupport;
                polygonLocalSupport.X.RawValue = (fpVector2.X.RawValue * cosRaw >> 16) - (fpVector2.Y.RawValue * sinRaw >> 16);
                polygonLocalSupport.Y.RawValue = (fpVector2.X.RawValue * sinRaw >> 16) + (fpVector2.Y.RawValue * cosRaw >> 16);
            }
            polygonLocalSupport.X.RawValue += this.LocalTransform.Position.X.RawValue;
            polygonLocalSupport.Y.RawValue += this.LocalTransform.Position.Y.RawValue;
            return polygonLocalSupport;
        }

        internal unsafe FP CalculateInertiaFactor(FrameBase f, FPVector2? comOffset = null)
        {
            FP sqrMagnitude = (comOffset.GetValueOrDefault() - this.Centroid).SqrMagnitude;
            switch (this._type)
            {
                case Shape2DType.Circle:
                    sqrMagnitude.RawValue += this.Circle.Radius.RawValue * this.Circle.Radius.RawValue >> 17;
                    return sqrMagnitude;
                case Shape2DType.Polygon:
                    sqrMagnitude.RawValue += FPVector2.CalculatePolygonInertiaFactor(f.FindAsset(this.Polygon.AssetRef).Vertices).RawValue;
                    return sqrMagnitude;
                case Shape2DType.Box:
                    sqrMagnitude.RawValue += ((this.Box._extents.X.RawValue * this.Box._extents.X.RawValue >> 16) + (this.Box._extents.Y.RawValue * this.Box._extents.Y.RawValue >> 16)) * 21845L >> 16;
                    return sqrMagnitude;
                case Shape2DType.Edge:
                    sqrMagnitude.RawValue += this.Edge.Extent.RawValue * this.Edge.Extent.RawValue / FP._3.RawValue;
                    return sqrMagnitude;
                case Shape2DType.Compound:
                    Shape2D* shapesBuffer;
                    int count;
                    if (!this.Compound.GetShapes(f, out shapesBuffer, out count))
                        return new FP();
                    if (count == 1)
                        return shapesBuffer->CalculateInertiaFactor(f, comOffset);
                    FP fp = new FP();
                    for (int index = 0; index < count; ++index)
                    {
                        Shape2D* shape2DPtr = shapesBuffer + index;
                        fp += shape2DPtr->GetArea(f) * shape2DPtr->CalculateInertiaFactor(f, comOffset);
                    }
                    return fp / this.Compound._area;
                default:
                    Log.Error(string.Format("Can't compute the inertia factor of shape type {0}.", (object) this._type), Array.Empty<object>());
                    return new FP();
            }
        }

        internal FP GetArea(FrameBase frame)
        {
            switch (this._type)
            {
                case Shape2DType.Circle:
                    return new FP()
                    {
                        RawValue = (this.Circle.Radius.RawValue * this.Circle.Radius.RawValue >> 16) * FP.Pi.RawValue >> 16
                    };
                case Shape2DType.Polygon:
                    return FPVector2.CalculatePolygonArea(frame.FindAsset(this.Polygon.AssetRef).Vertices);
                case Shape2DType.Box:
                    return new FP()
                    {
                        RawValue = this.Box._extents.X.RawValue * this.Box._extents.Y.RawValue >> 14
                    };
                case Shape2DType.Edge:
                    return new FP()
                    {
                        RawValue = this.Edge.Extent.RawValue * 6553L >> 15
                    };
                case Shape2DType.Compound:
                    return this.Compound._area;
                default:
                    return new FP();
            }
        }

        public static unsafe void Serialize(void* ptr, FrameSerializer serializer)
        {
            Shape2D* shape2DPtr = (Shape2D*) ptr;
            serializer.Stream.Serialize((byte*) &shape2DPtr->_type);
            serializer.Stream.Serialize(&shape2DPtr->_padding1);
            serializer.Stream.Serialize(&shape2DPtr->UserTag);
            serializer.Stream.Serialize(&shape2DPtr->_padding2);
            if (shape2DPtr->_type == Shape2DType.Compound)
            {
                Shape2D.CompoundShape2D.Serialize((void*) &shape2DPtr->Compound, serializer);
            }
            else
            {
                Transform2D.Serialize((void*) &shape2DPtr->LocalTransform, serializer);
                Shape2D.PolygonShape.Serialize((void*) &shape2DPtr->Polygon, (IDeterministicFrameSerializer) serializer);
            }
        }

        public override int GetHashCode()
        {
            int num = (17 * 31 + this._type.GetHashCode()) * 31 + this.UserTag.GetHashCode();
            return this._type != Shape2DType.Compound ? (num * 31 + this.LocalTransform.GetHashCode()) * 31 + this.Polygon.GetHashCode() : num * 31 + this.Compound.GetHashCode();
        }

        /// <summary>
        /// A 2D shape type that can reference a group of other 2D shapes (including other compounds).
        /// Compound shape overlaps and physics colliders with compound shapes are supported.
        /// </summary>
        [StructLayout(LayoutKind.Explicit)]
        public struct CompoundShape2D
        {
            public const int SIZE = 56;
            private const int DEFAULT_CAPACITY = 4;
            [FieldOffset(0)]
            internal FPVector2 _centroid;
            [FieldOffset(24)]
            internal FP _area;
            [FieldOffset(32)]
            internal FP _broadRadius;
            [FramePrinter.Ptr(typeof (Shape2D))]
            [FieldOffset(40)]
            internal Ptr _shapesBuffer;
            [FieldOffset(44)]
            internal ushort _count;
            [FieldOffset(46)]
            internal ushort _capacity;
            [FieldOffset(48)]
            internal int _isPersistent;
            [FieldOffset(52)]
            private int _padding;

            /// <summary>If the buffer of shapes is persistently allocated.</summary>
            /// <remarks>Persistently allocated memory is not disposed automatically. Call <see cref="M:Quantum.Shape2D.CompoundShape2D.FreePersistent(Quantum.Core.FrameBase,System.Boolean)" /> when done using a persistent compound.</remarks>
            public bool IsPersistent
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._isPersistent == 1;
            }

            /// <summary>The number of shapes in the compound buffer.</summary>
            public int Count
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (int) this._count;
            }

            /// <summary>
            /// Creates a copy of another compound <paramref name="shape" />'s and any nested compound's buffers.
            /// The resultant shapes will not share data.
            /// </summary>
            /// <remarks>Any existing persistent compounds in this shape's hierarchy are disposed before copying.</remarks>
            /// <param name="f">The current frame being simulated.</param>
            /// <param name="shape">The compound shape that will be copied from.</param>
            public unsafe void CopyFrom(FrameBase f, ref Shape2D shape)
            {
                this.FreePersistent(f);
                this._count = (ushort) 0;
                this._broadRadius = shape.BroadRadius;
                this._centroid = shape.Centroid;
                this._area = shape.Compound._area;
                Shape2D* shapesBuffer;
                int count;
                if (!shape.Compound.GetShapes(f, out shapesBuffer, out count))
                    return;
                for (int index = 0; index < count; ++index)
                {
                    Shape2D* shape2DPtr = shapesBuffer + index;
                    if (shape2DPtr->_type == Shape2DType.Compound)
                    {
                        if (this._isPersistent != 1)
                            throw new NotImplementedException();
                        Shape2D persistentCompound = Shape2D.CreatePersistentCompound();
                        persistentCompound.Compound.CopyFrom(f, ref *shape2DPtr);
                        this.AddShape(f, ref persistentCompound, false);
                    }
                    else
                        this.AddShape(f, ref *shape2DPtr, false);
                }
            }

            /// <summary>
            /// Adds the <paramref name="shape" /> to the compound buffer.
            /// </summary>
            /// <remarks>Trying to add a compound shape that holds a buffer already reference in this shape's hierarchy will throw an error in debug mode.</remarks>
            /// <param name="f">The frame currently being simulated.</param>
            /// <param name="shape">The shape being added to the compound.</param>
            /// <param name="updateCompoundMeta">If the compound meta data (e.g. broad radius and centroid) should be automatically updated.</param>
            public unsafe void AddShape(FrameBase f, ref Shape2D shape, bool updateCompoundMeta = true)
            {
                this.EnsureCapacity(f)[this._count++] = shape;
                if (!updateCompoundMeta)
                    return;
                FPVector2 fpVector2;
                fpVector2.X.RawValue = this._centroid.X.RawValue * this._area.RawValue >> 16;
                fpVector2.Y.RawValue = this._centroid.Y.RawValue * this._area.RawValue >> 16;
                FP area = shape.GetArea(f);
                FPVector2 centroid = shape.Centroid;
                this._area.RawValue += area.RawValue;
                if (this._area.RawValue == 0L)
                {
                    this._centroid = new FPVector2();
                }
                else
                {
                    this._centroid.X.RawValue = (fpVector2.X.RawValue + (centroid.X.RawValue * area.RawValue >> 16) << 16) / this._area.RawValue;
                    this._centroid.Y.RawValue = (fpVector2.Y.RawValue + (centroid.Y.RawValue * area.RawValue >> 16) << 16) / this._area.RawValue;
                }
                long rawValue = shape.BroadRadius.RawValue;
                if (shape._type != Shape2DType.Compound && (shape.LocalTransform.Position.X.RawValue != 0L || shape.LocalTransform.Position.Y.RawValue != 0L))
                    rawValue += shape.LocalTransform.Position.Magnitude.RawValue;
                if (rawValue <= this._broadRadius.RawValue)
                    return;
                this._broadRadius.RawValue = rawValue;
            }

            /// <summary>
            /// Removes all shapes in a compound that have a <see cref="F:Quantum.Shape2D.UserTag" /> field matching the specified <paramref name="userTag" />.
            /// </summary>
            /// <param name="f">The frame currently being simulated.</param>
            /// <param name="userTag">
            /// A tag that specifies a shape or group of shapes.
            /// Any shape in a compound that has a matching <see cref="F:Quantum.Shape2D.UserTag" /> field will be removed.
            /// If none is specified, all shapes are removed.
            /// </param>
            /// <param name="resetCompoundMeta">If the compound meta data (e.g. broad radius, centroid) should be automatically reset if any shapes are removed.</param>
            /// <param name="removeFromNested">If shapes should also be removed from any compound shape nested inside this.</param>
            /// <returns>True if at least one shape has been removed by this method.</returns>
            public unsafe bool RemoveShapes(
                FrameBase f,
                int? userTag = null,
                bool resetCompoundMeta = true,
                bool removeFromNested = true)
            {
                if (this._count == (ushort) 0)
                    return false;
                Shape2D* shape2DPtr1 = (Shape2D*) f.Heap->Void(this._shapesBuffer);
                bool flag = false;
                if (userTag.HasValue)
                {
                    int num1 = userTag.Value;
                    int num2 = 0;
                    while (num2 < (int) this._count)
                    {
                        Shape2D* shape2DPtr2 = shape2DPtr1 + num2;
                        if (shape2DPtr2->_type == Shape2DType.Compound & removeFromNested)
                            flag = flag || shape2DPtr2->Compound.RemoveShapes(f, userTag, resetCompoundMeta);
                        if ((int) shape2DPtr2->UserTag == num1)
                        {
                            if (shape2DPtr2->_type == Shape2DType.Compound)
                                shape2DPtr2->Compound.FreePersistent(f);
                            --this._count;
                            flag = true;
                            if (num2 < (int) this._count)
                                *shape2DPtr2 = shape2DPtr1[this._count];
                        }
                        else
                            ++num2;
                    }
                    if (flag & resetCompoundMeta)
                        this.ResetCompoundMeta(f);
                }
                else
                {
                    for (int index = 0; index < (int) this._count; ++index)
                    {
                        Shape2D* shape2DPtr3 = shape2DPtr1 + index;
                        if (shape2DPtr3->_type == Shape2DType.Compound)
                            shape2DPtr3->Compound.FreePersistent(f);
                    }
                    this._count = (ushort) 0;
                    flag = true;
                    if (resetCompoundMeta)
                    {
                        this._broadRadius = new FP();
                        this._area = new FP();
                        this._centroid = new FPVector2();
                    }
                }
                return flag;
            }

            /// <summary>
            /// Gets a pointer to the buffer of shapes added to this compound.
            /// </summary>
            /// <param name="f">The frame currently being simulated.</param>
            /// <param name="shapesBuffer">A pointer to the buffer of shapes added to this compound. Null if the compound doesn't have shapes.</param>
            /// <param name="count">The number of shapes in the <paramref name="shapesBuffer" />.</param>
            /// <returns>True if the buffer has at least one shape</returns>
            public unsafe bool GetShapes(FrameBase f, out Shape2D* shapesBuffer, out int count)
            {
                count = (int) this._count;
                if (this._count > (ushort) 0)
                {
                    shapesBuffer = (Shape2D*) f.Heap->Void(this._shapesBuffer);
                    return true;
                }
                shapesBuffer = (Shape2D*) null;
                return false;
            }

            /// <summary>
            /// Recomputes the compound shape broad radius, centroid and area sum (the later is used internally).
            /// </summary>
            /// <param name="f">The frame currently being simulated.</param>
            public unsafe void ResetCompoundMeta(FrameBase f)
            {
                this._centroid = new FPVector2();
                this._area = new FP();
                this._broadRadius = new FP();
                if (this._count <= (ushort) 0)
                    return;
                Shape2D* shape2DPtr1 = (Shape2D*) f.Heap->Void(this._shapesBuffer);
                FPVector2 fpVector2 = new FPVector2();
                for (int index = 0; index < (int) this._count; ++index)
                {
                    Shape2D* shape2DPtr2 = shape2DPtr1 + index;
                    FP area = shape2DPtr2->GetArea(f);
                    this._area.RawValue += area.RawValue;
                    FPVector2 centroid = shape2DPtr2->Centroid;
                    fpVector2.X.RawValue += centroid.X.RawValue * area.RawValue >> 16;
                    fpVector2.Y.RawValue += centroid.Y.RawValue * area.RawValue >> 16;
                    long rawValue = shape2DPtr2->BroadRadius.RawValue;
                    if (shape2DPtr2->_type != Shape2DType.Compound && (shape2DPtr2->LocalTransform.Position.X.RawValue != 0L || shape2DPtr2->LocalTransform.Position.Y.RawValue != 0L))
                        rawValue += shape2DPtr2->LocalTransform.Position.Magnitude.RawValue;
                    if (rawValue > this._broadRadius.RawValue)
                        this._broadRadius.RawValue = rawValue;
                }
                this._centroid.X.RawValue = (fpVector2.X.RawValue << 16) / this._area.RawValue;
                this._centroid.Y.RawValue = (fpVector2.Y.RawValue << 16) / this._area.RawValue;
            }

            /// <summary>
            /// Dispose any persistently allocated memory for this compound shape.
            /// </summary>
            /// <param name="f">The frame currently being simulated.</param>
            /// <param name="freeNestedPersistent">If nested compound shapes should also have persistent buffers disposed.</param>
            public unsafe void FreePersistent(FrameBase f, bool freeNestedPersistent = true)
            {
                if (this._shapesBuffer.Offset == 0)
                    return;
                if (freeNestedPersistent)
                {
                    Shape2D* shapesBuffer;
                    int count;
                    this.GetShapes(f, out shapesBuffer, out count);
                    for (int index = 0; index < count; ++index)
                    {
                        Shape2D* shape2DPtr = shapesBuffer + index;
                        if (shape2DPtr->_type == Shape2DType.Compound)
                            shape2DPtr->Compound.FreePersistent(f);
                    }
                }
                if (this._isPersistent != 1)
                    return;
                f.Heap->Free(this._shapesBuffer);
                this._shapesBuffer = new Ptr();
                this._count = (ushort) 0;
                this._capacity = (ushort) 0;
                this._broadRadius = new FP();
                this._centroid = new FPVector2();
            }

            private unsafe Shape2D* EnsureCapacity(FrameBase f, int? capacity = null)
            {
                int val2 = capacity ?? (int) this._count + 1;
                if ((int) this._capacity < val2)
                {
                    if (this._shapesBuffer.Offset != 0)
                    {
                        Shape2D* src = (Shape2D*) f.Heap->Void(this._shapesBuffer);
                        int num = Math.Max((int) this._capacity * 2, val2);
                        f.FrameMetaData->TrackAllocationInternal(f, this._shapesBuffer);
                        Shape2D* dest = (Shape2D*) f.Heap->AllocateAndClear(sizeof (Shape2D) * num);
                        Native.Utils.Copy((void*) dest, (void*) src, sizeof (Shape2D) * (int) this._capacity);
                        Shape2D* ptr = dest;
                        this._capacity = (ushort) num;
                        this._shapesBuffer = f.Heap->Void((void*) ptr);
                    }
                    else
                    {
                        int num = Math.Max(4, val2);
                        this._shapesBuffer = f.Heap->AllocateAndClearPtr(64 * num);
                        this._capacity = (ushort) num;
                    }
                }
                return (Shape2D*) f.Heap->Void(this._shapesBuffer);
            }

            /// <summary>
            /// Returns true if this compound shape doesn't already reference the <paramref name="ptr" /> in it's hierarchy.
            /// </summary>
            [Conditional("DEBUG")]
            private unsafe void AssertExistingReference(FrameBase f, Ptr ptr)
            {
                if (this._count == (ushort) 0)
                    return;
                if (this._shapesBuffer.Offset == ptr.Offset)
                {
                    Log.Error("Another reference to the same shapes buffer was found in the compound shape hierarchy.", Array.Empty<object>());
                }
                else
                {
                    Shape2D* shape2DPtr = (Shape2D*) f.Heap->Void(this._shapesBuffer);
                    for (int index = 0; index < (int) this._count; ++index)
                    {
                        int type = (int) shape2DPtr[index]._type;
                    }
                }
            }

            public static unsafe void Serialize(void* ptr, FrameSerializer serializer)
            {
                Shape2D.CompoundShape2D* compoundShape2DPtr = (Shape2D.CompoundShape2D*) ptr;
                serializer.Stream.Serialize(&compoundShape2DPtr->_isPersistent);
                FPVector2.Serialize((void*) &compoundShape2DPtr->_centroid, (IDeterministicFrameSerializer) serializer);
                FP.Serialize((void*) &compoundShape2DPtr->_area, (IDeterministicFrameSerializer) serializer);
                FP.Serialize((void*) &compoundShape2DPtr->_broadRadius, (IDeterministicFrameSerializer) serializer);
                serializer.Stream.Serialize(&compoundShape2DPtr->_count);
                serializer.Stream.Serialize(&compoundShape2DPtr->_capacity);
                FrameSerializer.PtrKey key;
                if (!serializer.PointerSerialize(&compoundShape2DPtr->_shapesBuffer, out key))
                    return;
                Shape2D* shape2DPtr = (Shape2D*) serializer.PointerResolve(key, sizeof (Shape2D) * (int) compoundShape2DPtr->_capacity);
                for (int index = 0; index < (int) compoundShape2DPtr->_count; ++index)
                    Shape2D.Serialize((void*) (shape2DPtr + index), serializer);
            }

            public override int GetHashCode() => ((((((17 * 31 + this._isPersistent.GetHashCode()) * 31 + this._centroid.GetHashCode()) * 31 + this._area.GetHashCode()) * 31 + this._broadRadius.GetHashCode()) * 31 + this._count.GetHashCode()) * 31 + this._capacity.GetHashCode()) * 31 + this._shapesBuffer.GetHashCode();
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct CircleShape
        {
            public const int SIZE = 8;
            [FieldOffset(0)]
            public FP Radius;

            public static unsafe void Serialize(void* ptr, IDeterministicFrameSerializer serializer) => FP.Serialize((void*) &((Shape2D.CircleShape*) ptr)->Radius, serializer);

            public override int GetHashCode() => 17 * 31 + this.Radius.GetHashCode();
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct PolygonShape
        {
            public const int SIZE = 32;
            [FieldOffset(0)]
            internal FP _broadRadius;
            [FieldOffset(8)]
            internal AssetRefPolygonCollider _assetRef;
            [FieldOffset(16)]
            internal FPVector2 _centroid;

            public AssetRefPolygonCollider AssetRef
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._assetRef;
            }

            public FP BroadRadius
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._broadRadius;
            }

            public FPVector2 Centroid
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._centroid;
            }

            public void SetPolygonCollider(PolygonCollider collider)
            {
                this._broadRadius = collider.Radius;
                this._centroid = collider.Centroid;
                this._assetRef = (AssetRefPolygonCollider) collider;
            }

            public static unsafe void Serialize(void* ptr, IDeterministicFrameSerializer serializer)
            {
                Shape2D.PolygonShape* polygonShapePtr = (Shape2D.PolygonShape*) ptr;
                FP.Serialize((void*) &polygonShapePtr->_broadRadius, serializer);
                AssetRefPolygonCollider.Serialize((void*) &polygonShapePtr->_assetRef, serializer);
                FPVector2.Serialize((void*) &polygonShapePtr->_centroid, serializer);
            }

            public override int GetHashCode() => ((17 * 31 + this._broadRadius.GetHashCode()) * 31 + this._assetRef.GetHashCode()) * 31 + this._centroid.GetHashCode();
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct BoxShape
        {
            public const int SIZE = 24;
            [FieldOffset(0)]
            internal FP _broadRadius;
            [FieldOffset(8)]
            internal FPVector2 _extents;

            public FP BroadRadius
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._broadRadius;
            }

            public FPVector2 Extents
            {
                [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this._extents;
                [MethodImpl(MethodImplOptions.AggressiveInlining)] set
                {
                    this._broadRadius = value.Magnitude;
                    this._extents = value;
                }
            }

            public static unsafe void Serialize(void* ptr, IDeterministicFrameSerializer serializer)
            {
                Shape2D.BoxShape* boxShapePtr = (Shape2D.BoxShape*) ptr;
                FP.Serialize((void*) &boxShapePtr->_broadRadius, serializer);
                FPVector2.Serialize((void*) &boxShapePtr->_extents, serializer);
            }

            public override int GetHashCode() => (17 * 31 + this._broadRadius.GetHashCode()) * 31 + this._extents.GetHashCode();
        }

        [StructLayout(LayoutKind.Explicit)]
        public struct EdgeShape
        {
            public const int SIZE = 8;
            [FieldOffset(0)]
            public FP Extent;

            public static unsafe void Serialize(void* ptr, IDeterministicFrameSerializer serializer) => FP.Serialize((void*) &((Shape2D.EdgeShape*) ptr)->Extent, serializer);

            public override int GetHashCode() => 17 * 31 + this.Extent.GetHashCode();
        }
    }
}